package com.virjar.hermes.hermesadmin.controller;

import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.virjar.hermes.hermesadmin.dao.DeviceAppMapper;
import com.virjar.hermes.hermesadmin.dao.DeviceMapper;
import com.virjar.hermes.hermesadmin.model.CommonRes;
import com.virjar.hermes.hermesadmin.model.Device;
import com.virjar.hermes.hermesadmin.model.DeviceApp;
import com.virjar.hermes.hermesadmin.model.ReportModel;
import com.virjar.hermes.hermesadmin.servicemanager.AppServiceRegistry;
import com.virjar.hermes.hermesadmin.util.Constant;
import com.virjar.hermes.hermesadmin.util.ReturnUtil;
import com.virjar.hermes.hermesadmin.util.URLEncodeUtil;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.SocketConfig;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.util.EntityUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Created by virjar on 2018/8/25.
 */
@Slf4j
@RestController
@RequestMapping("/device")
public class DeviceController {

    @Resource
    private DeviceMapper deviceMapper;

    @Resource
    private DeviceAppMapper deviceAppMapper;

    @Resource
    private AppServiceRegistry appServiceRegistry;

    @GetMapping("/list")
    @ApiOperation(value = "列表,分页显示所有的设备")
    @ResponseBody
    public CommonRes<Page<Device>> list(@PageableDefault Pageable pageable) {
        Page<Device> page = new PageImpl<>(deviceMapper.selectPage(pageable),
                pageable, deviceMapper.selectCount());
        return ReturnUtil.success(page);
    }


    @ApiOperation(value = "获取设备配置", notes = "agent访问该接口,agent会定时拉去最新的配置")
    @GetMapping("/deviceConfig")
    @ResponseBody
    public CommonRes<List<DeviceApp>> deviceConfig(@RequestParam("mac") String mac) {
        List<DeviceApp> deviceApps = deviceAppMapper.selectByMacOrService(mac, null);
        return ReturnUtil.success(deviceApps);
    }


    @ApiOperation(value = "上报设备信息", notes = "Android设备会定时上报存活状态,同时会上报当前在线的服务列表")
    @ApiImplicitParam(name = "reportModel", value = "reportModel", dataType = "com.virjar.hermes.hermesadmin.model.ReportModel", paramType = "body")
    @PostMapping("/report")
    @ResponseBody
    public CommonRes<?> report(@RequestBody ReportModel reportModel, HttpServletRequest httpServletRequest) {
        log.info("report request:" + JSONObject.toJSONString(reportModel));
        if (StringUtils.isBlank(reportModel.getAgentServerIP())
                || reportModel.getAgentServerPort() == 0) {
            return ReturnUtil.failed("agent ip & port not present");
        }
        if (StringUtils.isBlank(reportModel.getMac())) {
            return ReturnUtil.failed("agent device id  present");
        }

        Device device = deviceMapper.selectByMac(reportModel.getMac());
        boolean newDevice = false;
        if (device == null) {
            newDevice = true;
            device = new Device();
        }
        device.setName(reportModel.getBrand());
        device.setBrand(reportModel.getBrand());
        device.setIp(reportModel.getAgentServerIP());
        device.setPort(reportModel.getAgentServerPort());
        device.setMac(reportModel.getMac());
        device.setSystemVersion(reportModel.getSystemVersion());
        device.setVisibleIp(httpServletRequest.getRemoteAddr());
        if (newDevice) {
            deviceMapper.insert(device);
        } else {
            deviceMapper.updateByPrimaryKeySelective(device);
        }


        Set<String> onlineServices = Sets.newHashSet(reportModel.getOnlineServices());
        List<DeviceApp> deviceApps = deviceAppMapper.selectByMacOrService(device.getMac(), null);
        for (DeviceApp deviceApp : deviceApps) {
            onlineServices.remove(deviceApp.getAppPackage());
        }

        //自动注册
        for (String packageName : onlineServices) {
            DeviceApp deviceApp = new DeviceApp();
            deviceApp.setAppPackage(packageName);
            deviceApp.setDeviceId(device.getId());
            deviceApp.setDeviceMac(device.getMac());
            deviceApp.setStatus(Constant.serviceStatusOnline);
            deviceAppMapper.insert(deviceApp);
        }

        appServiceRegistry.handleReport(reportModel);
        return ReturnUtil.success("");
    }


    @ApiOperation(value = "调用某个服务", notes = "支持get/post,content type支持json和urlencoding,其他方式不支持")
    @ApiImplicitParam(name = "invoke_package", value = "invoke_package,代表一个apk的package,该参数必须存", dataType = "java.lang.String", paramType = "query")
    @RequestMapping(value = "/invoke", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public CommonRes<?> invoke(HttpServletRequest httpServletRequest) throws IOException {
        String contentType = httpServletRequest.getContentType();
        String service = httpServletRequest.getParameter("invoke_package");
        Integer timeOut = NumberUtils.toInt(httpServletRequest.getParameter("invoke_timeOut"));
        String requestBody = joinParam(httpServletRequest.getParameterMap());
        if (httpServletRequest.getMethod().equalsIgnoreCase("post")
                && StringUtils.containsIgnoreCase(contentType, "application/json;charset=utf8")) {
            requestBody = IOUtils.toString(httpServletRequest.getInputStream());
            if (StringUtils.isBlank(service)) {
                JSONObject jsonObject = JSONObject.parseObject(requestBody);
                service = jsonObject.getString("invoke_package");
                timeOut = jsonObject.getInteger("invoke_timeOut");
            }
        }
        if (StringUtils.isBlank(service)) {
            return ReturnUtil.failed("param: {invoke_package} not present");
        }
        String mac = appServiceRegistry.rollPoling(service);
        if (StringUtils.isBlank(mac)) {
            return ReturnUtil.failed("no service online");
        }
        Device device = deviceMapper.selectByMac(mac);
        if (device == null) {
            appServiceRegistry.offline(service, mac);
            return ReturnUtil.failed("system error,can not find device mode for choose device id");
        }

        if (timeOut == null || timeOut < 1) {
            //默认5s的超时时间
            timeOut = 5000;
        }
        if (StringUtils.isBlank(contentType)) {
            contentType = "application/x-www-form-urlencoded";
        }

        if (device.getIp().equalsIgnoreCase(device.getVisibleIp())) {
            //在同一个网络环境下,使用http请求,client->server的模式,对服务器压力小
            return forwardByHttpProtocol(device, service, requestBody, timeOut, contentType);
        } else {
            //非同一网段,无法直接请求内网server,考虑长链接方案,由于server会和client一直保持tcp链接,这将会限制
            //server的最大并发数量,当此模式下手机过多,则需要考虑水平扩容
            return forwardByNetty(device, service, requestBody, timeOut, contentType);
        }
    }

    private CommonRes<?> forwardByNetty(Device device, String service, String requestBody, int timeOut, String contentType) {
        //TODO 这里提供netty的方案
        return ReturnUtil.failed("not implemented");
    }

    private CommonRes<?> forwardByHttpProtocol(Device device, String service, String requestBody, int timeOut, String contentType) {
        //http://192.168.2.55:5597/invoke?invoke_package=com.tencent.weishi&key=%E5%B0%8F%E5%A7%90%E5%A7%90
        String requestURL = buildRequestURL(device, service);
        try {
            HttpPost httpPost = new HttpPost(requestURL);
            RequestConfig.Builder builder = RequestConfig.custom().setSocketTimeout(timeOut)
                    .setConnectTimeout(timeOut)
                    //.setProxy(new HttpHost("127.0.0.1", 8888))
                    .setConnectionRequestTimeout(timeOut);

            httpPost.setConfig(builder.build());
            httpPost.setEntity(new StringEntity(requestBody, contentType));
            if (httpClient == null) {
                createHttpClient();
            }
            HttpResponse httpResponse = httpClient.execute(httpPost);
            JSONObject ret = JSONObject.parseObject(EntityUtils.toString(httpResponse.getEntity()));
            Integer status = ret.getInteger("status");
            if (status == Constant.status_service_not_available) {
                appServiceRegistry.offline(service, device.getMac());
            }
            return ReturnUtil.from(ret);
        } catch (IOException e) {
            log.error("forward failed", e);
            return ReturnUtil.failed("timeOut");
        }
    }

    private HttpClient httpClient = null;

    private synchronized void createHttpClient() {
        if (httpClient != null) {
            return;
        }
        SocketConfig socketConfig = SocketConfig.custom().setSoKeepAlive(true).setSoLinger(-1).setSoReuseAddress(false)
                .setTcpNoDelay(true).build();
        httpClient = HttpClientBuilder.create().setMaxConnTotal(500).setMaxConnPerRoute(25)
                .setDefaultSocketConfig(socketConfig)
                .setRedirectStrategy(new LaxRedirectStrategy())
                .build();
    }

    private String buildRequestURL(Device device, String targetService) {
        return "http://" + device.getIp() + ":" + device.getPort() + "/invoke?invoke_package=" + URLEncodeUtil.encode(targetService);
    }


    private Joiner joiner = Joiner.on('&').skipNulls();

    private String joinParam(Map<String, String[]> params) {
        if (params == null || params.isEmpty()) {
            return null;
        }
        return joiner.join(Iterables.transform(params.entrySet(), new Function<Map.Entry<String, String[]>, String>() {
            @Override
            public String apply(Map.Entry<String, String[]> input) {
                final String name = input.getKey();
                String[] inputValue = input.getValue();
                List<String> encodeItem = Lists.newArrayListWithExpectedSize(inputValue.length);
                for (String value : inputValue) {
                    encodeItem.add(URLEncodeUtil.encode(name) + "=" + URLEncodeUtil.encode(value));
                }
                return joiner.join(encodeItem);
            }
        }));

    }
}
